package com.beiding.natgo.client;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;

@Slf4j
public class ClientWorker {

    private Thread coreThread;

    private Selector selector;

    private SessionHolder sessionHolder;

    //阻塞队列
    private Queue<Runnable> taskQueue = new ConcurrentLinkedQueue<>();

    //通道map
    private Map<String, ChanelHolder> chanelHolderMap = new HashMap<>();

    private boolean goFlag = true;

    public ClientWorker(SessionHolder sessionHolder) {
        this.sessionHolder = sessionHolder;
    }

    public void start() {
        coreThread = new Thread() {
            @Override
            public void run() {
                exe();
            }
        };
        coreThread.start();
    }

    private void exe() {

        try {
            selector = Selector.open();
            handle();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void handle() {
        try {
            while (goFlag) {
                int select = selector.select();

                Runnable poll;
                while ((poll = taskQueue.poll()) != null) {
                    poll.run();
                }

                if (select == 0) {
                    continue;
                }
                Set<SelectionKey> selectionKeys = selector.selectedKeys();

                Iterator<SelectionKey> iterator = selectionKeys.iterator();

                while (iterator.hasNext()) {
                    SelectionKey next = iterator.next();
                    iterator.remove();
                    if (next.isValid()) {
                        if (next.isReadable()) {
                            ChanelHolder attachment = (ChanelHolder) next.attachment();
                            attachment.handleRead();
                        } else if (next.isConnectable()) {
                            SocketChannel channel = (SocketChannel) next.channel();
                            if (channel.finishConnect()) {
                                log.info("连接成功,注册读事件");
                                ChanelHolder attachment = (ChanelHolder) next.attachment();
                                channel.register(selector, SelectionKey.OP_READ, attachment);
                                String id = attachment.getId();
                                chanelHolderMap.put(id, attachment);
                                sessionHolder.sendCreateSucess(id);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void createChanel(String id, String host, Integer port) {

        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            SocketAddress socketAddress = new InetSocketAddress(host, port);
            socketChannel.connect(socketAddress);
            ChanelHolder chanelHolder = new ChanelHolder(this, socketChannel, sessionHolder, id);

            offerTask(() -> {
                try {
                    SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_CONNECT, chanelHolder);
                    chanelHolder.setSelectionKey(selectionKey);
                } catch (Exception e) {
                    sessionHolder.sendCreateFail(id);
                    e.printStackTrace();
                }
            });

        } catch (Exception e) {
            sessionHolder.sendCreateFail(id);
            e.printStackTrace();
        }

    }

    private void offerTask(Runnable runnable) {
        taskQueue.offer(runnable);
        selector.wakeup();
    }

    public void closeChanel(String chanelId) {
        log.info("收到关闭连接请求{}",chanelId);
        ChanelHolder chanelHolder = this.chanelHolderMap.remove(chanelId);
        if (chanelHolder != null) {
            SocketChannel socketChannel = chanelHolder.getSocketChannel();
            if (socketChannel != null) {
                try {
                    socketChannel.shutdownInput();
                    socketChannel.shutdownOutput();
                    socketChannel.close();
                    chanelHolder.getSelectionKey().cancel();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void removeChanelAndSendClose(String id) {
        ChanelHolder chanelHolder = this.chanelHolderMap.remove(id);
        if (chanelHolder != null) {
            sessionHolder.sendCloseChanel(id);
        }
    }

    public void writeData(String chanelId, byte[] data) {
        ChanelHolder chanelHolder = this.chanelHolderMap.get(chanelId);
        if (chanelHolder != null) {
            chanelHolder.writeData(data);
        } else {
            sessionHolder.sendCloseChanel(chanelId);
        }
    }

}
